package com.ls.pattern.proxy.myproxy;


import org.apache.commons.lang3.StringUtils;

import javax.tools.JavaCompiler;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.File;
import java.io.FileWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 代理对象生成类
 *
 * @author 挥之以墨
 */
public class MyProxy {

    private static final String LN = System.lineSeparator();

    public static Object newProxyInstance(MyClassLoader loader, Class<?>[] interfaces, MyInvocationHanlder h) {
        // 动态生成java源码
        String src = generateSrc(interfaces);
        // 将源码输入到磁盘
        String path = MyProxy.class.getResource("").getPath();
        File file = new File(path + "$Proxy0.java");
        try (FileWriter fos = new FileWriter(file)) {
            fos.write(src);
        } catch (Exception e) {
            e.printStackTrace();
        }
        try {
            JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
            StandardJavaFileManager manage = compiler.getStandardFileManager(null, null, null);
            Iterable iterable = manage.getJavaFileObjects(file);
            JavaCompiler.CompilationTask task = compiler.getTask(null, manage, null, null, null, iterable);
            task.call();
            manage.close();

            // 将.class文件加载到JVM
            Class proxyClazz = loader.findClass("$Proxy0");
            Constructor constructor = proxyClazz.getConstructor(MyInvocationHanlder.class);
            file.delete();

            // 返回字节码重组后的代理对象
            return constructor.newInstance(h);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }

    private static String generateSrc(Class<?>[] interfaces) {
        StringBuilder sb = new StringBuilder();
        sb.append("package com.ls.pattern.proxy.myproxy;").append(LN)
                .append("import com.ls.pattern.proxy.myproxy.client.MyPerson;").append(LN)
                .append("import java.lang.reflect.*;").append(LN)
                .append("public class $Proxy0 implements ").append(interfaces[0].getName()).append("{").append(LN)
                .append("MyInvocationHanlder h;").append(LN)
                .append("public $Proxy0(MyInvocationHanlder h) { ").append(LN)
                .append("this.h = h;")
                .append("}").append(LN);
        for (Method m : interfaces[0].getMethods()) {
            Class<?>[] params = m.getParameterTypes();
            List<String> paramNameList = new ArrayList<>();
            List<String> paramValueList = new ArrayList<>();
            List<String> paramClassList = new ArrayList<>();
            for (int i = 0; i < params.length; i++) {
                String typeName = params[i].getTypeName();
                String valueName = "var" + i;
                paramNameList.add(typeName + " " + valueName);
                paramClassList.add(typeName + ".class");
                paramValueList.add(valueName);
            }

            sb.append("public " + m.getReturnType().getName() + " " + m.getName() + "(" + StringUtils.join(paramNameList, ",")
                    + ") {" + LN).append("try{").append(LN)
                    .append("Method m = " + interfaces[0].getName() + ".class.getMethod (\"" + m.getName() +
                            "\",new Class[]{" + StringUtils.join(paramClassList, ",") + "});" + LN)
                    .append((hasReturnValue(m.getReturnType()) ? "return " : "") +
                            getCaseCode("this.h.invoke(this,m,new Object[]{" + StringUtils.join(paramValueList, ",") + "})", m.getReturnType()) + ";" + LN)
                    .append("}catch(Error _ex) { }")
                    .append("catch(Throwable e){").append(LN)
                    .append("throw new UndeclaredThrowableException(e);").append(LN)
                    .append("}")
                    .append(getReturnEmptyCode(m.getReturnType()))
                    .append("}");
        }


        sb.append("}");

        return sb.toString();
    }

    private static String getReturnEmptyCode(Class<?> returnClass) {
        if (mappings.containsKey(returnClass)) {
            return "return 0;";
        } else if (returnClass == void.class) {
            return "";
        } else {
            return "return null;";
        }
    }

    /**
     * 首字母小写
     *
     * @param simpleName 待转换首字母的字符串
     * @return 首字母小写的字符串
     */
    private static String toLowerFirstCase(String simpleName) {
        char[] chars = simpleName.toCharArray();
        chars[0] += 32;
        return String.valueOf(chars);
    }

    /**
     * 判断方法返回值是否为void
     *
     * @param clazz 返回值类型
     */
    private static boolean hasReturnValue(Class<?> clazz) {
        return clazz != void.class;
    }

    private static Map<Class, Class> mappings = new HashMap<>();

    static {
        mappings.put(int.class, Integer.class);
    }

    private static String getCaseCode(String code, Class<?> returnClass) {
        if (mappings.containsKey(returnClass)) {
            return "((" + mappings.get(returnClass).getName() + ")" + code + ")." + returnClass.getSimpleName() +
                    "Value()";
        }
        return code;
    }

}
